package edu.jhmi.tools.omssa_gui;// Ravi Tharakan

/**
 * @author Ravi Tharakan
 * @author Lennart Martens
 */

import edu.jhmi.tools.omssa_gui.omssa_readers.OMSSAEnzymeReader;
import edu.jhmi.tools.omssa_gui.omssa_readers.OMSSAModificationReader;
import edu.jhmi.tools.omssa_gui.omssa_readers.OMSSA_GUI_Modification;
import edu.jhmi.tools.omssa_gui.utilities.ExtensionFileFilter;
import edu.jhmi.tools.omssa_gui.utilities.SwingWorker;
import org.xmlpull.v1.XmlPullParserException;

import javax.swing.*;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.ListSelectionEvent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Properties;

// class instantiation: since OMSSAGUI is the overall class, it will need to both show the
// GUI with features and respond to user commands with ActionEvents, so inherits from the
// superclass JPanel and also implements the interface ActionListener

public class OMSSAGUI extends JPanel {

    private static final String OMSSA_LOCATION_FILE = "omssa_location.oml";
    private static final String DEFAULTS_FILE = "default_OMSSA_GUI.properties";

    private static final String DATABASE_FILE = "DATABASE_FILE";
    private static final String ENZYME = "ENZYME";
    private static final String FIXED_MODIFICATIONS = "FIXED_MODIFICATIONS";
    private static final String VARIABLE_MODIFICATIONS = "VARIABLE_MODIFICATIONS";
    private static final String MISSED_CLEAVAGES = "MISSED_CLEAVAGES";
    private static final String PRECURSOR_MASS_TOLERANCE = "PRECURSOR_MASS_TOLERANCE";
    private static final String FRAGMENT_MASS_TOLERANCE = "FRAGMENT_MASS_TOLERANCE";
    private static final String EVALUE_CUTOFF = "EVALUE_CUTOFF";
    private static final String MAXIMUM_HITLIST_LENGTH = "MAXIMUM_HITLIST_LENGTH";
    private static final String PRECURSOR_CHARGE_LOWER_BOUND = "PRECURSOR_CHARGE_LOWER_BOUND";
    private static final String PRECURSOR_CHARGE_UPPER_BOUND = "PRECURSOR_CHARGE_UPPER_BOUND";
    private static final String PRECURSOR_CHARGE_TO_CONSIDER_MULTIPLY_CHARGED_FRAGMENTS = "PRECURSOR_CHARGE_TO_CONSIDER_MULTIPLY_CHARGED_FRAGMENTS";

    private static final String MODIFICATION_SEPARATOR = "//";

    /**
     * Simple date formatter.
     */
    private static final SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy, HH:mm");

    /**
     * Boolean to flag creation of the GUI components. A bit messy, but keeps the rest of the
     * code manageable. Only influences the 'enableGUI' and 'refreshModLists' methods.
     */
    private boolean iGUICreated = false;

    /**
     * The modification reader.
     */
    private OMSSAModificationReader modifications = null;


    // outside frame of GUI
    private JFrame parentFrame;
    
    // TextFields to show the files which have been selected
    private JTextField txtOMSSA_location = new JTextField();

    private JTextField txtDatabaseFile, txtMGFFile, txtOutputFile, txtMissedCleavages,
                       txtPrecursorTolerance, txtFragmentTolerance, txtChargeLowerBound,
                       txtChargeUpperBound, txtPrecChargeForFragmentCharge, txtEValue, txtHitlist;

    // Buttons.
    private JButton btnBrowseOMSSA_location = null;
    private JButton btnBrowseDatabase = null;
    private JButton btnBrowseMGF = null;
    private JButton btnBrowseOutput = null;

    // The lists with variable and fixed modifications.
    private JList lstVarMods, lstFixedMods;

    // A drop-down menu for enzyme
    private JComboBox cmbEnzyme;

    /**
     * This is the constructor for the GUI panel. It sets the close operation,
     * creates the frame, and instantiates the cumulative
     * strings.
     *
     * @param  aParent  JFrame with the parent frame for this panel.
     */
    public OMSSAGUI(JFrame aParent){
        // Initialize the parent frame.
        this.parentFrame = aParent;

        // First see if we can locate the OMSSA home folder.
        String omssa_dir = loadOMSSALocationFile();
        if(omssa_dir == null || !validateOMSSALocation(new File(omssa_dir))) {
            invalidOMSSALocationSpecified();
        } else {
            // Correct OMSSA location. Just load the stuff we need.
            File omssa_root_dir = new File(omssa_dir.trim());
            // Initialize the OMSSA location.
            newOMSSALocationSpecified(omssa_root_dir);
        }

        // calls the addFeatures method below
        addFeatures();

        // See if there are any defaults that need to be specified.
        Properties props = new Properties();
        try {
            InputStream stream = this.getClass().getClassLoader().getResourceAsStream(DEFAULTS_FILE);
            if(stream != null) {
                props.load(stream);
            }
            stream.close();
        } catch(IOException ioe) {
            // Do nothing.
        }
        // See if it worked.
        if(props.size() == 0) {
            JOptionPane.showMessageDialog(this, "Unable to load default values", "Defaults file not found.", JOptionPane.INFORMATION_MESSAGE);
        } else {
            // Set properties on the GUI.
            setScreenProps(props);
        }
    }

    /**
     * This creates the features and adds them to the page.
     */
    private void addFeatures() {

        // Label for the OMSSA location.
        JLabel lblOMSSA_location = new JLabel("OMSSA location: ");
        // Labels for the input files.
        JLabel lblDatabaseFile = new JLabel("FASTA Database file: ");
        JLabel lblMGFFile = new JLabel("Spectra file:  ");
        // ... and for the output file.
        JLabel lblOutputFile = new JLabel("OMSSA result file: ");

        // Labels for the number of selected mods.
        final JLabel lblNbrSelectedFixedMods = new JLabel(" No modifications selected ");
        final JLabel lblNbrSelectedVariableMods = new JLabel(" No modifications selected ");

        // Determine max label length for the files.
        int maxLabelWidth = Math.max(Math.max(lblDatabaseFile.getPreferredSize().width, lblMGFFile.getPreferredSize().width), Math.max(lblOutputFile.getPreferredSize().width, lblOMSSA_location.getPreferredSize().width));
        // Make all labels equally wide.
        lblOMSSA_location.setPreferredSize(new Dimension(maxLabelWidth, lblOMSSA_location.getPreferredSize().height));
        lblDatabaseFile.setPreferredSize(new Dimension(maxLabelWidth, lblDatabaseFile.getPreferredSize().height));
        lblMGFFile.setPreferredSize(new Dimension(maxLabelWidth, lblMGFFile.getPreferredSize().height));
        lblOutputFile.setPreferredSize(new Dimension(maxLabelWidth, lblOutputFile.getPreferredSize().height));

        // OMSSA location textfield.
        txtOMSSA_location.setEditable(false);
        txtOMSSA_location.setMaximumSize(new Dimension(txtOMSSA_location.getMaximumSize().width, txtOMSSA_location.getPreferredSize().height));
        // OMSSA location browse button.
        btnBrowseOMSSA_location = new JButton("Browse...");
        btnBrowseOMSSA_location.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                browseOMSSALocationPressed(false);
            }
        });


        // Set up textfield which will show the file after it
        // has been chosen; set uneditable.
        txtDatabaseFile = new JTextField();
        txtDatabaseFile.setEditable(false);
        txtDatabaseFile.setMaximumSize(new Dimension(txtDatabaseFile.getMaximumSize().width, txtDatabaseFile.getPreferredSize().height));
        txtMGFFile = new JTextField();
        txtMGFFile.setEditable(false);
        txtMGFFile.setMaximumSize(new Dimension(txtMGFFile.getMaximumSize().width, txtMGFFile.getPreferredSize().height));

        // instantiate button for loading the database
        btnBrowseDatabase = new JButton("Browse...");
        btnBrowseDatabase.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                // First check whether a file has already been selected.
                // If so, start from that file's parent.
                File startLocation = null;
                if(txtDatabaseFile.getText()!= null && !txtDatabaseFile.getText().trim().equals("")) {
                    File temp = new File(txtDatabaseFile.getText());
                    startLocation = temp.getParentFile();
                }
                JFileChooser fc = new JFileChooser(startLocation);
                fc.setFileFilter(new ExtensionFileFilter("fasta", false));
                int result = fc.showOpenDialog(OMSSAGUI.this);
	            if (result == JFileChooser.APPROVE_OPTION) {
		            File file = fc.getSelectedFile();
		            txtDatabaseFile.setText(file.getAbsolutePath());
	            }
            }
        });

        // Instantiate button for browsing for the MGF file.
        btnBrowseMGF = new JButton("Browse...");
        btnBrowseMGF.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // First check whether a file has already been selected.
                // If so, start from that file's parent.
                File startLocation = null;
                if(txtMGFFile.getText()!= null && !txtMGFFile.getText().trim().equals("")) {
                    File temp = new File(txtMGFFile.getText());
                    startLocation = temp.getParentFile();
                }
                JFileChooser fc = new JFileChooser(startLocation);
                fc.setFileFilter(new ExtensionFileFilter("pkl", false));
                fc.setFileFilter(new ExtensionFileFilter("dta", false));
                fc.setFileFilter(new ExtensionFileFilter("mgf", false));
                int result = fc.showOpenDialog(OMSSAGUI.this);
                if (result == JFileChooser.APPROVE_OPTION) {
                    File file = fc.getSelectedFile();
                    txtMGFFile.setText(file.getAbsolutePath());
                }
            }
        });


        // The OMSSA location panel.
        JPanel jpanOMSSA_location = new JPanel();
        jpanOMSSA_location.setLayout(new BoxLayout(jpanOMSSA_location, BoxLayout.X_AXIS));
        jpanOMSSA_location.setBorder(BorderFactory.createTitledBorder("OMSSA location"));
        jpanOMSSA_location.add(Box.createHorizontalStrut(5));
        jpanOMSSA_location.add(lblOMSSA_location);
        jpanOMSSA_location.add(Box.createHorizontalStrut(5));
        jpanOMSSA_location.add(txtOMSSA_location);
        jpanOMSSA_location.add(Box.createHorizontalStrut(5));
        jpanOMSSA_location.add(btnBrowseOMSSA_location);
        jpanOMSSA_location.add(Box.createHorizontalGlue());
        jpanOMSSA_location.setMaximumSize(new Dimension(jpanOMSSA_location.getMaximumSize().width, jpanOMSSA_location.getPreferredSize().height));

        // The database file selection panel.
        JPanel jpanDBSelection = new JPanel();
        jpanDBSelection.setLayout(new BoxLayout(jpanDBSelection, BoxLayout.X_AXIS));
        jpanDBSelection.add(Box.createHorizontalStrut(5));
        jpanDBSelection.add(lblDatabaseFile);
        jpanDBSelection.add(Box.createHorizontalStrut(5));
        jpanDBSelection.add(txtDatabaseFile);
        jpanDBSelection.add(Box.createHorizontalStrut(5));
        jpanDBSelection.add(btnBrowseDatabase);
        jpanDBSelection.add(Box.createHorizontalGlue());
        jpanDBSelection.setMaximumSize(new Dimension(jpanDBSelection.getMaximumSize().width, jpanDBSelection.getPreferredSize().height));

        // Now the MGF file selection panel.
        JPanel jpanMGFSelection = new JPanel();
        jpanMGFSelection.setLayout(new BoxLayout(jpanMGFSelection, BoxLayout.X_AXIS));
        jpanMGFSelection.add(Box.createHorizontalStrut(5));
        jpanMGFSelection.add(lblMGFFile);
        jpanMGFSelection.add(Box.createHorizontalStrut(5));
        jpanMGFSelection.add(txtMGFFile);
        jpanMGFSelection.add(Box.createHorizontalStrut(5));
        jpanMGFSelection.add(btnBrowseMGF);
        jpanMGFSelection.add(Box.createHorizontalGlue());
        jpanMGFSelection.setMaximumSize(new Dimension(jpanMGFSelection.getMaximumSize().width, jpanMGFSelection.getPreferredSize().height));

        // Now group this section.
        JPanel jpanInputFiles = new JPanel();
        jpanInputFiles.setLayout(new BoxLayout(jpanInputFiles, BoxLayout.Y_AXIS));
        jpanInputFiles.setBorder(BorderFactory.createTitledBorder("Input files"));
        jpanInputFiles.add(Box.createVerticalStrut(5));
        jpanInputFiles.add(jpanDBSelection);
        jpanInputFiles.add(Box.createVerticalStrut(5));
        jpanInputFiles.add(jpanMGFSelection);
        jpanInputFiles.setMaximumSize(new Dimension(jpanInputFiles.getMaximumSize().width, jpanInputFiles.getPreferredSize().height));

        // Output file textfield.
        txtOutputFile = new JTextField();
        txtOutputFile.setEditable(false);
        txtOutputFile.setMaximumSize(new Dimension(txtOutputFile.getMaximumSize().width, txtOutputFile.getPreferredSize().height));

        // Instantiate button for browsing for an output file.
        btnBrowseOutput = new JButton("Browse...");
        btnBrowseOutput.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // call the filechooser object and run the show save dialog;
                // this shows the filechooser, and also
	            // automatically returns an int, which is used to check if the
                // 'ok' button has been pushed;
	            // if it has, get the selected file as a File object, read the
                // name of the file and display the string in the text box

                // First check whether a file has already been selected.
                // If so, start from that file's parent.
                File startLocation = null;
                if(txtOutputFile.getText()!= null && !txtOutputFile.getText().trim().equals("")) {
                    File temp = new File(txtOutputFile.getText());
                    startLocation = temp.getParentFile();
                }
                boolean lbContinue = true;
                while(lbContinue) {
                    JFileChooser fc = new JFileChooser(startLocation);
                    fc.setFileFilter(new ExtensionFileFilter("omx", false));
                    int result = fc.showSaveDialog(OMSSAGUI.this);
                    if (result == JFileChooser.APPROVE_OPTION) {
                        File file = fc.getSelectedFile();
                        String outputFile = file.getAbsolutePath();
                        startLocation = file.getParentFile();
                        if(!outputFile.endsWith(".omx")) {
                            outputFile += ".omx";
                        }
                        // Check for existence.
                        if(new File(outputFile).exists()) {
                            int outcome = JOptionPane.showConfirmDialog(OMSSAGUI.this, new String[]{"File '" + outputFile + "' exists!", "Overwrite?"}, "File exists!", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
                            if(outcome != JOptionPane.YES_OPTION) {
                                continue;
                            }
                        }
                        lbContinue = false;
                        txtOutputFile.setText(outputFile);
                    } else {
                        lbContinue = false;
                    }
                }
            }
        });

        // The MGF file selection panel.
        JPanel jpanOutputFile = new JPanel();
        jpanOutputFile.setLayout(new BoxLayout(jpanOutputFile, BoxLayout.X_AXIS));
        jpanOutputFile.setBorder(BorderFactory.createTitledBorder("Output file"));
        jpanOutputFile.add(Box.createHorizontalStrut(5));
        jpanOutputFile.add(lblOutputFile);
        jpanOutputFile.add(Box.createHorizontalStrut(5));
        jpanOutputFile.add(txtOutputFile);
        jpanOutputFile.add(Box.createHorizontalStrut(5));
        jpanOutputFile.add(btnBrowseOutput);
        jpanOutputFile.add(Box.createHorizontalGlue());
        jpanOutputFile.setMaximumSize(new Dimension(jpanOutputFile.getMaximumSize().width, jpanOutputFile.getPreferredSize().height));

        // Next up: the modification lists.
        // Two labels.
        String pressCTRL = "Multiple selection by pressing CTRL";
        JLabel lblFixedMods = new JLabel(pressCTRL);
        lblFixedMods.setFont(lblFixedMods.getFont().deriveFont(java.awt.Font.ITALIC));
        JLabel lblVarMods = new JLabel(pressCTRL);
        lblVarMods.setFont(lblVarMods.getFont().deriveFont(java.awt.Font.ITALIC));

        lblNbrSelectedFixedMods.setFont(lblFixedMods.getFont().deriveFont(java.awt.Font.ITALIC));
        lblNbrSelectedVariableMods.setFont(lblFixedMods.getFont().deriveFont(java.awt.Font.ITALIC));

        // The lists proper.
        DefaultListModel dlm = new DefaultListModel();
        populateListModel(dlm, modifications.getModifications());
        lstFixedMods = new JList(dlm);
        lstFixedMods.setMaximumSize(new Dimension(lstFixedMods.getMaximumSize().width, lstFixedMods.getPreferredSize().height));
        lstFixedMods.addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                String selected = null;
                int selection = lstFixedMods.getSelectedIndices().length;
                if (selection == 0) {
                    selected = " No modifications selected ";
                } if(selection == 1) {
                    selected = " 1 modification selected ";
                } else {
                    selected = " " + selection + " modifications selected ";
                }
                lblNbrSelectedFixedMods.setText(selected);
            }
        });
        dlm = new DefaultListModel();
        populateListModel(dlm, modifications.getModifications());
        lstVarMods = new JList(dlm);
        lstVarMods.addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                String selected = null;
                int selection = lstVarMods.getSelectedIndices().length;
                if (selection == 0) {
                    selected = " No modifications selected ";
                } else if (selection == 1) {
                    selected = " 1 modification selected ";
                } else {
                    selected = " " + selection + " modifications selected ";
                }
                lblNbrSelectedVariableMods.setText(selected);
            }
        });

        // JPanels to center the labels.
        JPanel jpanFixedLabel = new JPanel();
        jpanFixedLabel.setLayout(new BoxLayout(jpanFixedLabel, BoxLayout.X_AXIS));
        jpanFixedLabel.add(Box.createHorizontalGlue());
        jpanFixedLabel.add(lblFixedMods);
        jpanFixedLabel.add(Box.createHorizontalGlue());
        jpanFixedLabel.setMaximumSize(new Dimension(jpanFixedLabel.getMaximumSize().width, jpanFixedLabel.getPreferredSize().height));

        JPanel jpanVarLabel = new JPanel();
        jpanVarLabel.setLayout(new BoxLayout(jpanVarLabel, BoxLayout.X_AXIS));
        jpanVarLabel.add(Box.createHorizontalGlue());
        jpanVarLabel.add(lblVarMods);
        jpanVarLabel.add(Box.createHorizontalGlue());
        jpanVarLabel.setMaximumSize(new Dimension(jpanVarLabel.getMaximumSize().width, jpanVarLabel.getPreferredSize().height));

        JPanel jpanFixedLabelCount = new JPanel();
        jpanFixedLabelCount.setLayout(new BoxLayout(jpanFixedLabelCount, BoxLayout.X_AXIS));
        jpanFixedLabelCount.add(Box.createHorizontalGlue());
        jpanFixedLabelCount.add(lblNbrSelectedFixedMods);
        jpanFixedLabelCount.add(Box.createHorizontalGlue());
        jpanFixedLabelCount.setMaximumSize(new Dimension(jpanFixedLabel.getMaximumSize().width, jpanFixedLabel.getPreferredSize().height));

        JPanel jpanVarLabelCount = new JPanel();
        jpanVarLabelCount.setLayout(new BoxLayout(jpanVarLabelCount, BoxLayout.X_AXIS));
        jpanVarLabelCount.add(Box.createHorizontalGlue());
        jpanVarLabelCount.add(lblNbrSelectedVariableMods);
        jpanVarLabelCount.add(Box.createHorizontalGlue());
        jpanVarLabelCount.setMaximumSize(new Dimension(jpanVarLabel.getMaximumSize().width, jpanVarLabel.getPreferredSize().height));

        // To each a panel.
        JPanel jpanFixedMods = new JPanel();
        jpanFixedMods.setLayout(new BoxLayout(jpanFixedMods, BoxLayout.Y_AXIS));
        jpanFixedMods.setBorder(BorderFactory.createTitledBorder("Fixed modifications"));
        jpanFixedMods.add(Box.createVerticalStrut(5));
        jpanFixedMods.add(jpanFixedLabel);
        jpanFixedMods.add(Box.createVerticalStrut(5));
        jpanFixedMods.add(new JScrollPane(lstFixedMods));
        jpanFixedMods.add(Box.createVerticalStrut(5));
        jpanFixedMods.add(jpanFixedLabelCount);
        jpanFixedMods.add(Box.createVerticalStrut(5));

        JPanel jpanVarMods = new JPanel();
        jpanVarMods.setLayout(new BoxLayout(jpanVarMods, BoxLayout.Y_AXIS));
        jpanVarMods.setBorder(BorderFactory.createTitledBorder("Variable modifications"));
        jpanVarMods.add(Box.createVerticalStrut(5));
        jpanVarMods.add(jpanVarLabel);
        jpanVarMods.add(Box.createVerticalStrut(5));
        jpanVarMods.add(new JScrollPane(lstVarMods));
        jpanVarMods.add(Box.createVerticalStrut(5));
        jpanVarMods.add(jpanVarLabelCount);
        jpanVarMods.add(Box.createVerticalStrut(5));

        // And group them.
        JPanel jpanModifications = new JPanel();
        jpanModifications.setLayout(new BoxLayout(jpanModifications, BoxLayout.X_AXIS));
        jpanModifications.add(jpanFixedMods);
        jpanModifications.add(Box.createHorizontalStrut(5));
        jpanModifications.add(jpanVarMods);

        // Labels for lower half components.
        JLabel lblEnzyme = new JLabel("Select enzyme: ");
        JLabel lblMissedCleavages = new JLabel("Allowed missed cleavages: ");

        JLabel lblPrecursorTolerance = new JLabel("Precursor mass tolerance (Da): ");
        JLabel lblFragmentTolerance = new JLabel("Fragment mass tolerance (Da): ");

        JLabel lblChargeLowerBound = new JLabel("Lower bound: ");
        JLabel lblChargeUpperBound = new JLabel("Upper bound: ");

        JLabel lblEValue = new JLabel("E-value cutoff: ");
        JLabel lblHitlist = new JLabel("Maximum hitlist length: ");

        // Left-hand side labels.
        maxLabelWidth = Math.max(  Math.max(lblEnzyme.getPreferredSize().width, lblPrecursorTolerance.getPreferredSize().width),
                                       Math.max(lblChargeLowerBound.getPreferredSize().width, lblEValue.getPreferredSize().width));
        lblPrecursorTolerance.setPreferredSize(new Dimension(maxLabelWidth, lblPrecursorTolerance.getPreferredSize().height));
        lblChargeLowerBound.setPreferredSize(new Dimension(maxLabelWidth, lblChargeLowerBound.getPreferredSize().height));
        lblEValue.setPreferredSize(new Dimension(maxLabelWidth, lblEValue.getPreferredSize().height));

        // Right-hand side labels.
        maxLabelWidth = Math.max(  Math.max(lblMissedCleavages.getPreferredSize().width, lblFragmentTolerance.getPreferredSize().width),
                                       Math.max(lblChargeUpperBound.getPreferredSize().width, lblHitlist.getPreferredSize().width));
        lblMissedCleavages.setPreferredSize(new Dimension(maxLabelWidth, lblMissedCleavages.getPreferredSize().height));
        lblFragmentTolerance.setPreferredSize(new Dimension(maxLabelWidth, lblFragmentTolerance.getPreferredSize().height));
        lblChargeUpperBound.setPreferredSize(new Dimension(maxLabelWidth, lblChargeUpperBound.getPreferredSize().height));
        lblHitlist.setPreferredSize(new Dimension(maxLabelWidth, lblHitlist.getPreferredSize().height));


        // Enzyme combobox.
        cmbEnzyme = new JComboBox(OMSSAEnzymeReader.getEnzymes(null));
        cmbEnzyme.setMaximumSize(new Dimension(cmbEnzyme.getPreferredSize().width, cmbEnzyme.getPreferredSize().height));
        // Text field for missed cleavages.
        txtMissedCleavages = new JTextField(5);
        txtMissedCleavages.setMaximumSize(new Dimension(txtMissedCleavages.getPreferredSize().width, txtMissedCleavages.getPreferredSize().height));

        // Panel for enzyme selection.
        JPanel jpanEnzymeSelection = new JPanel();
        jpanEnzymeSelection.setLayout(new BoxLayout(jpanEnzymeSelection, BoxLayout.X_AXIS));
        jpanEnzymeSelection.add(Box.createHorizontalStrut(5));
        jpanEnzymeSelection.add(lblEnzyme);
        jpanEnzymeSelection.add(Box.createHorizontalStrut(10));
        jpanEnzymeSelection.add(cmbEnzyme);
        jpanEnzymeSelection.add(Box.createHorizontalGlue());

        // Panel for missed cleavages.
        JPanel jpanMissedCleavage = new JPanel();
        jpanMissedCleavage.setLayout(new BoxLayout(jpanMissedCleavage, BoxLayout.X_AXIS));
        jpanMissedCleavage.add(Box.createHorizontalStrut(5));
        jpanMissedCleavage.add(lblMissedCleavages);
        jpanMissedCleavage.add(Box.createHorizontalStrut(5));
        jpanMissedCleavage.add(txtMissedCleavages);
        jpanMissedCleavage.add(Box.createHorizontalGlue());

        // The panel.
        JPanel jpanEnzyme = new JPanel();
        jpanEnzyme.setLayout(new BoxLayout(jpanEnzyme, BoxLayout.X_AXIS));
        jpanEnzyme.setBorder(BorderFactory.createTitledBorder("Proteolytic enzyme"));
        jpanEnzyme.add(jpanEnzymeSelection);
        jpanEnzyme.add(Box.createHorizontalStrut(5));
        jpanEnzyme.add(jpanMissedCleavage);
        jpanEnzyme.setMaximumSize(new Dimension(jpanEnzyme.getMaximumSize().width, jpanEnzyme.getPreferredSize().height));

        // Now the mass tolerances.
        // Textfields
        txtPrecursorTolerance = new JTextField(5);
        txtPrecursorTolerance.setMaximumSize(new Dimension(txtPrecursorTolerance.getPreferredSize().width, txtPrecursorTolerance.getPreferredSize().height));
        txtFragmentTolerance = new JTextField(5);
        txtFragmentTolerance.setMaximumSize(new Dimension(txtFragmentTolerance.getPreferredSize().width, txtFragmentTolerance.getPreferredSize().height));

        // Precursor panel.
        JPanel jpanPrecursorTolerance = new JPanel();
        jpanPrecursorTolerance.setLayout(new BoxLayout(jpanPrecursorTolerance, BoxLayout.X_AXIS));
        jpanPrecursorTolerance.add(Box.createHorizontalStrut(5));
        jpanPrecursorTolerance.add(lblPrecursorTolerance);
        jpanPrecursorTolerance.add(Box.createHorizontalStrut(5));
        jpanPrecursorTolerance.add(txtPrecursorTolerance);
        jpanPrecursorTolerance.add(Box.createHorizontalGlue());

        // Fragment panel.
        JPanel jpanFragmentTolerance = new JPanel();
        jpanFragmentTolerance.setLayout(new BoxLayout(jpanFragmentTolerance, BoxLayout.X_AXIS));
        jpanFragmentTolerance.add(Box.createHorizontalStrut(5));
        jpanFragmentTolerance.add(lblFragmentTolerance);
        jpanFragmentTolerance.add(Box.createHorizontalStrut(5));
        jpanFragmentTolerance.add(txtFragmentTolerance);
        jpanFragmentTolerance.add(Box.createHorizontalGlue());

        // Panel.
        JPanel jpanTolerances= new JPanel();
        jpanTolerances.setLayout(new BoxLayout(jpanTolerances, BoxLayout.X_AXIS));
        jpanTolerances.setBorder(BorderFactory.createTitledBorder("Mass tolerances"));
        jpanTolerances.add(jpanPrecursorTolerance);
        jpanTolerances.add(Box.createHorizontalStrut(5));
        jpanTolerances.add(jpanFragmentTolerance);
        jpanTolerances.setMaximumSize(new Dimension(jpanTolerances.getMaximumSize().width, jpanTolerances.getPreferredSize().height));

        // Lower and upper bound for precursor charge state.
        txtChargeLowerBound = new JTextField(5);
        txtChargeLowerBound.setMaximumSize(new Dimension(txtChargeLowerBound.getPreferredSize().width, txtChargeLowerBound.getPreferredSize().height));
        txtChargeUpperBound = new JTextField(5);
        txtChargeUpperBound.setMaximumSize(new Dimension(txtChargeUpperBound.getPreferredSize().width, txtChargeUpperBound.getPreferredSize().height));

        // Precursor panel.
        JPanel jpanChargeLowerBound = new JPanel();
        jpanChargeLowerBound.setLayout(new BoxLayout(jpanChargeLowerBound, BoxLayout.X_AXIS));
        jpanChargeLowerBound.add(Box.createHorizontalStrut(5));
        jpanChargeLowerBound.add(lblChargeLowerBound);
        jpanChargeLowerBound.add(Box.createHorizontalStrut(5));
        jpanChargeLowerBound.add(txtChargeLowerBound);
        jpanChargeLowerBound.add(Box.createHorizontalGlue());

        // Fragment panel.
        JPanel jpanChargeUpperBound = new JPanel();
        jpanChargeUpperBound.setLayout(new BoxLayout(jpanChargeUpperBound, BoxLayout.X_AXIS));
        jpanChargeUpperBound.add(Box.createHorizontalStrut(5));
        jpanChargeUpperBound.add(lblChargeUpperBound);
        jpanChargeUpperBound.add(Box.createHorizontalStrut(5));
        jpanChargeUpperBound.add(txtChargeUpperBound);
        jpanChargeUpperBound.add(Box.createHorizontalGlue());

        // Panel.
        JPanel jpanChargeBounds = new JPanel();
        jpanChargeBounds.setLayout(new BoxLayout(jpanChargeBounds, BoxLayout.X_AXIS));
        jpanChargeBounds.add(jpanChargeLowerBound);
        jpanChargeBounds.add(Box.createHorizontalStrut(5));
        jpanChargeBounds.add(jpanChargeUpperBound);
        jpanChargeBounds.setMaximumSize(new Dimension(jpanChargeBounds.getMaximumSize().width, jpanChargeBounds.getPreferredSize().height));

        // Minimal precursor charge for charged fragments.
        JLabel lblPrecChargeForFragmentCharge = new JLabel("Minimum charge to consider multiply charged fragments: ");
        txtPrecChargeForFragmentCharge = new JTextField(5);
        txtPrecChargeForFragmentCharge.setMaximumSize(new Dimension(txtPrecChargeForFragmentCharge.getPreferredSize().width, txtPrecChargeForFragmentCharge.getPreferredSize().height));
        JPanel jpanPrecChargeForFragmentCharge = new JPanel();
        jpanPrecChargeForFragmentCharge.setLayout(new BoxLayout(jpanPrecChargeForFragmentCharge, BoxLayout.X_AXIS));
        jpanPrecChargeForFragmentCharge.add(Box.createHorizontalStrut(5));
        jpanPrecChargeForFragmentCharge.add(lblPrecChargeForFragmentCharge);
        jpanPrecChargeForFragmentCharge.add(Box.createHorizontalStrut(5));
        jpanPrecChargeForFragmentCharge.add(txtPrecChargeForFragmentCharge);
        jpanPrecChargeForFragmentCharge.add(Box.createHorizontalGlue());

        // Total charge panel.
        JPanel jpanTotalCharge = new JPanel();
        jpanTotalCharge.setLayout(new BoxLayout(jpanTotalCharge, BoxLayout.Y_AXIS));
        jpanTotalCharge.setBorder(BorderFactory.createTitledBorder("Precursor charge bounds"));
        jpanTotalCharge.add(jpanChargeBounds);
        jpanTotalCharge.add(Box.createVerticalStrut(5));
        jpanTotalCharge.add(jpanPrecChargeForFragmentCharge);
        jpanTotalCharge.setMaximumSize(new Dimension(jpanTotalCharge.getMaximumSize().width, jpanTotalCharge.getPreferredSize().height));

        // E-value and maximum hit list size.
        txtEValue = new JTextField(5);
        txtEValue.setMaximumSize(new Dimension(txtEValue.getPreferredSize().width, txtEValue.getPreferredSize().height));
        txtHitlist = new JTextField(5);
        txtHitlist.setMaximumSize(new Dimension(txtHitlist.getPreferredSize().width, txtHitlist.getPreferredSize().height));

        // E-value panel.
        JPanel jpanEValue = new JPanel();
        jpanEValue.setLayout(new BoxLayout(jpanEValue, BoxLayout.X_AXIS));
        jpanEValue.add(Box.createHorizontalStrut(5));
        jpanEValue.add(lblEValue);
        jpanEValue.add(Box.createHorizontalStrut(5));
        jpanEValue.add(txtEValue);
        jpanEValue.add(Box.createHorizontalGlue());

        // Hit list panel.
        JPanel jpanHitlist = new JPanel();
        jpanHitlist.setLayout(new BoxLayout(jpanHitlist, BoxLayout.X_AXIS));
        jpanHitlist.add(Box.createHorizontalStrut(5));
        jpanHitlist.add(lblHitlist);
        jpanHitlist.add(Box.createHorizontalStrut(5));
        jpanHitlist.add(txtHitlist);
        jpanHitlist.add(Box.createHorizontalGlue());

        // Panel.
        JPanel jpanEValueAndHitlist = new JPanel();
        jpanEValueAndHitlist.setLayout(new BoxLayout(jpanEValueAndHitlist, BoxLayout.X_AXIS));
        jpanEValueAndHitlist.setBorder(BorderFactory.createTitledBorder("E-value and hit list size"));
        jpanEValueAndHitlist.add(jpanEValue);
        jpanEValueAndHitlist.add(Box.createHorizontalStrut(5));
        jpanEValueAndHitlist.add(jpanHitlist);
        jpanEValueAndHitlist.setMaximumSize(new Dimension(jpanEValueAndHitlist.getMaximumSize().width, jpanEValueAndHitlist.getPreferredSize().height));


        // Align Left-hand panels of lower half.
        int minPanelWidth = jpanFixedMods.getPreferredSize().width;
        jpanEnzymeSelection.setPreferredSize(new Dimension(minPanelWidth, jpanEnzymeSelection.getMinimumSize().height));
        jpanPrecursorTolerance.setPreferredSize(new Dimension(minPanelWidth, jpanPrecursorTolerance.getMinimumSize().height));
        jpanChargeLowerBound.setPreferredSize(new Dimension(minPanelWidth, jpanChargeLowerBound.getMinimumSize().height));
        jpanEValue.setPreferredSize(new Dimension(minPanelWidth, jpanEValue.getMinimumSize().height));



       // Main panel.
        JPanel jpanMain = new JPanel();
        jpanMain.setLayout(new BoxLayout(jpanMain, BoxLayout.Y_AXIS));
        jpanMain.add(jpanOMSSA_location);
        jpanMain.add(Box.createVerticalStrut(10));
        jpanMain.add(jpanInputFiles);
        jpanMain.add(jpanOutputFile);
        jpanMain.add(Box.createVerticalStrut(10));
        jpanMain.add(jpanModifications);
        jpanMain.add(jpanEnzyme);
        jpanMain.add(Box.createVerticalStrut(10));
        jpanMain.add(jpanTolerances);
        jpanMain.add(Box.createVerticalStrut(10));
        jpanMain.add(jpanEValueAndHitlist);
        jpanMain.add(Box.createVerticalStrut(10));
        jpanMain.add(jpanTotalCharge);
        jpanMain.add(Box.createVerticalStrut(15));
        jpanMain.add(this.getButtonPanel());
        jpanMain.add(Box.createVerticalStrut(5));

        parentFrame.getContentPane().add(jpanMain, BorderLayout.CENTER);
        // Flag the fact that we now have an initialized GUI.
        iGUICreated = true;
    }

    /**
     * This mathod takes care of creating the frame and adds in the GUI panel.
     */
    public static void createandshowGUI() {
        JFrame frame = new JFrame("OMSSA GUI");
        final OMSSAGUI omssagui = new OMSSAGUI(frame);
        frame.addWindowListener(new WindowAdapter() {
            /**
             * Invoked when a window is in the process of being closed.
             * The close operation can be overridden at this point.
             */
            public void windowClosing(WindowEvent e) {
                omssagui.close(0);
            }
        });
        frame.setLocation(100, 100);
        frame.pack();
        // Pack is the minimal size, so add 20 pixels in each dimension.
        frame.setSize(new Dimension(frame.getSize().width+20, frame.getSize().height+20));
        frame.setVisible(true);
    }

    /**
     * This is the main function; it calls the function above; it
	 * also coordinates the thread safety (NOTE: this code modelled
	 * directly on code from java.sun.com)
     *
     * @param args  String[] with the start-up arguments.
     */
    public static void main(String[] args) {
	    javax.swing.SwingUtilities.invokeLater(new Runnable() {
		    public void run() {
		        createandshowGUI();
		    }
	    });
    }

    /**
     * This method creates the button panel.
     *
     * @return  JPanel with the buttons.
     */
    private JPanel getButtonPanel() {
        // Button components.

        // Button for saving current settings to a properties file.
        JButton btnSaveValues = new JButton("Save...");
        btnSaveValues.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                saveValuesPressed();
            }
        });

        // Button for loading a saved properties file.
        JButton btnLoadValues = new JButton("Load...");
        btnLoadValues.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                loadValuesPressed();
            }
        });

        // button for running the bat file as a dos command after written
        JButton btnRun = new JButton("Run");
        btnRun.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                runPressed();
            }
        });

        // Exit button.
        JButton btnExit = new JButton("Exit");
        btnExit.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                exitPressed();
            }
        });


        // The panel.
        JPanel jpanButtons = new JPanel();
        jpanButtons.setLayout(new BoxLayout(jpanButtons, BoxLayout.X_AXIS));
        jpanButtons.add(Box.createHorizontalGlue());
        jpanButtons.add(btnSaveValues);
        jpanButtons.add(Box.createHorizontalStrut(5));
        jpanButtons.add(btnLoadValues);
        jpanButtons.add(Box.createHorizontalStrut(5));
        jpanButtons.add(btnRun);
        jpanButtons.add(Box.createHorizontalStrut(5));
        jpanButtons.add(btnExit);
        jpanButtons.add(Box.createHorizontalStrut(5));
        jpanButtons.setMaximumSize(new Dimension(jpanButtons.getMaximumSize().width, jpanButtons.getPreferredSize().height));

        return jpanButtons;
    }

    /**
     * This method is called when the user clicks the 'Save...' button.
     */
    private void saveValuesPressed() {
        File selected = null;
        boolean complete = false;
        while(!complete) {
            File parent = null;
            if(selected != null) {
                parent = selected.getParentFile();
            }
            JFileChooser fc = new JFileChooser(parent);
            fc.setFileFilter(new ExtensionFileFilter("properties", false));
            int result = fc.showSaveDialog(OMSSAGUI.this);
            if (result == JFileChooser.APPROVE_OPTION) {
                selected = fc.getSelectedFile();
                // Make sure the file is appended with '.properties'
                if(!selected.getName().toLowerCase().endsWith(".properties")) {
                    selected = new File(selected.getParentFile(), selected.getName() + ".properties");
                }
                complete = true;
                if(selected.exists()) {
                    int choice = JOptionPane.showConfirmDialog(this, new String[] {"The file you specified already exists!", "Overwrite?"}, "File '" + selected.getName() + "' already exists!", JOptionPane.YES_NO_OPTION);
                    if(choice == JOptionPane.NO_OPTION) {
                        complete = false;
                    }
                }
            } else {
                return;
            }
        }
        saveScreenProps(selected);
    }

    /**
     * This method is called when the user clicks the 'Load...' button.
     */
    private void loadValuesPressed() {
        JFileChooser fc = new JFileChooser();
        fc.setFileFilter(new ExtensionFileFilter("properties", false));
        int result = fc.showOpenDialog(OMSSAGUI.this);
        if (result == JFileChooser.APPROVE_OPTION) {
            File file = fc.getSelectedFile();
            Properties props = loadProperties(file);
            setScreenProps(props);
            JOptionPane.showMessageDialog(this, "Loaded values from file '" + file.getName() + "'.", "Properties loaded", JOptionPane.INFORMATION_MESSAGE);
        }
    }

    /**
     * This method is called whenever the 'run' button is pressed.
     */
    private void runPressed() {
        // Validate inputs while gathering them.

        // First validate the input files.
        if(txtDatabaseFile.getText() == null || txtDatabaseFile.getText().trim().equals("")) {
            JOptionPane.showMessageDialog(this, "You need to specify a search database first!", "No search database found!", JOptionPane.WARNING_MESSAGE);
            btnBrowseDatabase.requestFocus();
            return;
        }

        // Check .fasta at end of string (formatdb takes only .fasta or asn.1, assuming not asn.1)
        if(!(txtDatabaseFile.getText().trim().endsWith(".fasta") || txtDatabaseFile.getText().trim().endsWith(".fas"))) {
            JOptionPane.showMessageDialog(this, "You need to specify a valid '.fasta' or '.fas' database file!", "No search database found!", JOptionPane.WARNING_MESSAGE);
            btnBrowseDatabase.requestFocus();
            return;
        }

        String dbFile = txtDatabaseFile.getText().trim();

        // Validate spectra file.
        if(txtMGFFile.getText() == null || txtMGFFile.getText().trim().equals("")) {
            JOptionPane.showMessageDialog(this, "You need to specify a spectra file first!", "No spectra file found!", JOptionPane.WARNING_MESSAGE);
            btnBrowseMGF.requestFocus();
            return;
        }

        // Check .mgf or .pkl or .dta at end of string
        if(!(txtMGFFile.getText().trim().endsWith(".mgf")) && !(txtMGFFile.getText().trim().endsWith(".dta")) && !(txtMGFFile.getText().trim().endsWith(".pkl")))  {
            JOptionPane.showMessageDialog(this, "You need to specify a valid .mgf, .dta or .pkl spectra file!", "No spectra file found!", JOptionPane.WARNING_MESSAGE);
            btnBrowseMGF.requestFocus();
            return;
        }

        String spectraFile = txtMGFFile.getText().trim();

        // Validate the output file.
        if(txtOutputFile.getText() == null || txtOutputFile.getText().trim().equals("")) {
            JOptionPane.showMessageDialog(this, "You need to specify an output file first!", "No output file found!", JOptionPane.WARNING_MESSAGE);
            btnBrowseOutput.requestFocus();
            return;
        }

        // Check .omx at end of string
        if(!(txtOutputFile.getText().trim().endsWith(".omx"))) {
            JOptionPane.showMessageDialog(this, "You need to specify a .omx output file!", "No output file found!", JOptionPane.WARNING_MESSAGE);
            btnBrowseOutput.requestFocus();
            return;
        }

        String outputFile = txtOutputFile.getText().trim();

        // Validate missed cleavages (multi-step validation).
        if(txtMissedCleavages.getText() == null || txtMissedCleavages.getText().trim().equals("")) {
            JOptionPane.showMessageDialog(this, "You need to specify a number of allowed misssed cleavages first!", "No allowed missed cleavages found!", JOptionPane.WARNING_MESSAGE);
            txtMissedCleavages.requestFocus();
            return;
        }

        // OK, see if it is an integer.
        int missedCleavages = -1;

        try {
            missedCleavages = Integer.parseInt(txtMissedCleavages.getText().trim());
        } catch(NumberFormatException nfe) {
            // Unparseable integer!
            JOptionPane.showMessageDialog(this, "You need to specify a positive whole number (zero or more) for the allowed misssed cleavages!", "Incorrect missed cleavages number found!", JOptionPane.WARNING_MESSAGE);
            txtMissedCleavages.requestFocus();
            return;
        }

        // And it should be zero or more.
        if(missedCleavages < 0) {
            JOptionPane.showMessageDialog(this, "You need to specify a positive whole number (zero or more) for the allowed misssed cleavages!", "No negative missed cleavages allowed!", JOptionPane.WARNING_MESSAGE);
            txtMissedCleavages.requestFocus();
            return;
        }

        // And not above 3.
        if (missedCleavages > 3) {
            JOptionPane.showMessageDialog(this, "You need to specify a positive whole number less than 4 for the allowed missed cleavages!", "Incorrect number of allowed missed cleavages!", JOptionPane.WARNING_MESSAGE);
            txtMissedCleavages.requestFocus();
            return;
        }

	// Validate precursor mass tolerances
        if(txtPrecursorTolerance.getText() == null || txtPrecursorTolerance.getText().trim().equals("")) {
            JOptionPane.showMessageDialog(this, "You need to specify a precursor mass tolerance first!", "No precursor mass tolerance found!", JOptionPane.WARNING_MESSAGE);
            txtPrecursorTolerance.requestFocus();
            return;
        }

        // OK, see if it is a number.
        float precursorTolerance = -1;

        try {
            precursorTolerance = Float.parseFloat(txtPrecursorTolerance.getText().trim());
        } catch(NumberFormatException nfe) {
            // Unparseable number!
            JOptionPane.showMessageDialog(this, "You need to specify a positive number (zero or more) for the precursor mass tolerance!", "Incorrect precursor mass tolerance found!", JOptionPane.WARNING_MESSAGE);
            txtPrecursorTolerance.requestFocus();
            return;
        }

        // And it should be zero or more.
        if(precursorTolerance < 0) {
            JOptionPane.showMessageDialog(this, "You need to specify a positive number (zero or more) for the precursor mass tolerance!", "Incorrect precursor mass tolerance found!", JOptionPane.WARNING_MESSAGE);
            txtPrecursorTolerance.requestFocus();
            return;
        }

	    // Validate fragment mass tolerances
        if(txtFragmentTolerance.getText() == null || txtFragmentTolerance.getText().trim().equals("")) {
            JOptionPane.showMessageDialog(this, "You need to specify a fragment mass tolerance first!", "No fragment mass tolerance found!", JOptionPane.WARNING_MESSAGE);
            txtFragmentTolerance.requestFocus();
            return;
        }

        // OK, see if it is a number.
        float fragmentTolerance = -1;

        try {
            fragmentTolerance = Float.parseFloat(txtFragmentTolerance.getText().trim());
        } catch(NumberFormatException nfe) {
            // Unparseable number!
            JOptionPane.showMessageDialog(this, "You need to specify a positive number (zero or more) for the fragment mass tolerance!", "Incorrect fragment mass tolerance found!", JOptionPane.WARNING_MESSAGE);
            txtFragmentTolerance.requestFocus();
            return;
        }

        // And it should be zero or more.
        if(fragmentTolerance < 0) {
            JOptionPane.showMessageDialog(this, "You need to specify a positive number (zero or more) for the fragment mass tolerance!", "Incorrect fragment mass tolerance found!", JOptionPane.WARNING_MESSAGE);
            txtFragmentTolerance.requestFocus();
            return;
        }

	    // Validate e-value cutoff
        if(txtEValue.getText() == null || txtEValue.getText().trim().equals("")) {
            JOptionPane.showMessageDialog(this, "You need to specify an e-value cutoff first!", "No e-value cutoff found!", JOptionPane.WARNING_MESSAGE);
            txtEValue.requestFocus();
            return;
        }

        // OK, see if it is a number.
        float eValue = -1;

        try {
            eValue = Float.parseFloat(txtEValue.getText().trim());
        } catch(NumberFormatException nfe) {
            // Unparseable number!
            JOptionPane.showMessageDialog(this, "You need to specify a positive number for the e-value cutoff!", "Incorrect e-value found!", JOptionPane.WARNING_MESSAGE);
            txtEValue.requestFocus();
            return;
        }

        // And it should be zero or more.
        if(eValue < 0) {
            JOptionPane.showMessageDialog(this, "You need to specify a positive number for the e-value cutoff!", "Incorrect e-value cutoff found!", JOptionPane.WARNING_MESSAGE);
            txtEValue.requestFocus();
            return;
        }


	// Validate maximum hitlist

        if(txtHitlist.getText() == null || txtHitlist.getText().trim().equals("")) {
            JOptionPane.showMessageDialog(this, "You need to specify a hit-list length first!", "No hit-list length found!", JOptionPane.WARNING_MESSAGE);
            txtHitlist.requestFocus();
            return;
        }

        // OK, see if it is an integer.
        int hitList = -1;

        try {
            hitList = Integer.parseInt(txtHitlist.getText().trim());
        } catch(NumberFormatException nfe) {
            // Unparseable number!
            JOptionPane.showMessageDialog(this, "You need to specify a positive number for the hit-list length!", "Incorrect hit-list length found!", JOptionPane.WARNING_MESSAGE);
            txtHitlist.requestFocus();
            return;
        }

        // And it should be zero or more.
        if(hitList < 0) {
            JOptionPane.showMessageDialog(this, "You need to specify a positive whole number for the hit-list length !", "Incorrect hit-list length found!", JOptionPane.WARNING_MESSAGE);
            txtHitlist.requestFocus();
            return;
        }

	    // Validate precursor charge lower bound

        if(txtChargeLowerBound.getText() == null || txtChargeLowerBound.getText().trim().equals("")) {
            JOptionPane.showMessageDialog(this, "You need to specify a lower bound for the precursor charge first!", "No lower bound of precursor charge found!", JOptionPane.WARNING_MESSAGE);
            txtChargeLowerBound.requestFocus();
            return;
        }

        // OK, see if it is an integer.
        int chargeLowerBound = -1;

        try {
            chargeLowerBound = Integer.parseInt(txtChargeLowerBound.getText().trim());
        } catch(NumberFormatException nfe) {
            // Unparseable number!
            JOptionPane.showMessageDialog(this, "You need to specify a positive integer for the lower bound of the precursor charge!", "Incorrect precursor charge lower bound found!", JOptionPane.WARNING_MESSAGE);
            txtChargeLowerBound.requestFocus();
            return;
        }

        if(chargeLowerBound <= 0) {
            JOptionPane.showMessageDialog(this, "You need to specify a positive integer for the lower bound of the precursor charge!", "Incorrect precursor charge found!", JOptionPane.WARNING_MESSAGE);
            txtChargeLowerBound.requestFocus();
            return;
        }

	    // Validate precursor charge upper bound
        if(txtChargeUpperBound.getText() == null || txtChargeUpperBound.getText().trim().equals("")) {
            JOptionPane.showMessageDialog(this, "You need to specify an upper bound for the precursor charge first!", "No upper bound of precursor charge found!", JOptionPane.WARNING_MESSAGE);
            txtChargeUpperBound.requestFocus();
            return;
        }

        // OK, see if it is an integer.
        int chargeUpperBound = -1;

        try {
            chargeUpperBound = Integer.parseInt(txtChargeUpperBound.getText().trim());
        } catch(NumberFormatException nfe) {
            // Unparseable number!
            JOptionPane.showMessageDialog(this, "You need to specify a positive integer for the upper bound of the precursor charge!", "Incorrect precursor charge upper bound found!", JOptionPane.WARNING_MESSAGE);
            txtChargeUpperBound.requestFocus();
            return;
        }

        if(chargeUpperBound <= 0) {
            JOptionPane.showMessageDialog(this, "You need to specify a positive integer for the upper bound of the precursor charge!", "Incorrect precursor charge found!", JOptionPane.WARNING_MESSAGE);
            txtChargeUpperBound.requestFocus();
            return;
        }

        if(chargeUpperBound < chargeLowerBound) {
            JOptionPane.showMessageDialog(this, "The minimum precursor charge must be lower than or equal to the maximum precursor charge!", "Incorrect minimum precursor charge found!", JOptionPane.WARNING_MESSAGE);
            txtChargeLowerBound.requestFocus();
            return;
        }

        // Validate minimum charge to consider multiply charged fragments
        if(txtPrecChargeForFragmentCharge.getText() == null || txtPrecChargeForFragmentCharge.getText().trim().equals("")) {
	    JOptionPane.showMessageDialog(this, "You need to specify a minimum charge to start considering multiply charged fragments first!", "No minimum for multiply charged fragments found!", JOptionPane.WARNING_MESSAGE);
            txtPrecChargeForFragmentCharge.requestFocus();
            return;
        }

        // OK, see if it is an integer.
        int minChargeForMultiplyCharged = -1;

        try {
            minChargeForMultiplyCharged = Integer.parseInt(txtPrecChargeForFragmentCharge.getText().trim());
        } catch(NumberFormatException nfe) {
            // Unparseable number!
            JOptionPane.showMessageDialog(this, "You need to specify a positive integer for the minimum charge to start considering multiply charged fragments first!", "No minimum for multiply charged fragments found!", JOptionPane.WARNING_MESSAGE);
            txtPrecChargeForFragmentCharge.requestFocus();
            return;
        }

	    // and it should be zero or more
        if(minChargeForMultiplyCharged < 0) {
            JOptionPane.showMessageDialog(this, "You need to specify a positive integer for the minimum charge to start considering multiply charged fragments first!", "Incorrect minimum for multiply charged fragments found!", JOptionPane.WARNING_MESSAGE);
            txtPrecChargeForFragmentCharge.requestFocus();
            return;
        }
	
	    // collect mods and check empty
        if (lstVarMods.isSelectionEmpty() && lstFixedMods.isSelectionEmpty()) {
            int confirm = JOptionPane.showConfirmDialog(this, "You have no modifications selected! Are you sure you want to continue?", "No modifications found!", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
            if (confirm == JOptionPane.NO_OPTION) {
                return;
            }
        }

        // get index of selected enzyme so can be passed as a number
        int selected_enzyme =  cmbEnzyme.getSelectedIndex();
        // get indices of fixed mods and variable mods
        int[] selected_fixedmods = lstFixedMods.getSelectedIndices();
        int[] selected_varmods = lstVarMods.getSelectedIndices();

        // The formatdb utility is asumed to be in the OMSSA installation folder.
        File omssa_folder = new File(txtOMSSA_location.getText().trim());
        // call formatdb class, check if run before and then start process
        File seqDBFile = new File(dbFile);
        FormatdbProcessBuilder fpb = new FormatdbProcessBuilder(seqDBFile, omssa_folder);
        boolean needFormatDB = fpb.needsFormatting();

        // call omssacl class
        OmssaclProcessBuilder opb = new OmssaclProcessBuilder(omssa_folder, spectraFile, outputFile, missedCleavages, precursorTolerance,
                                      fragmentTolerance, eValue, hitList, chargeLowerBound, chargeUpperBound,
                                      minChargeForMultiplyCharged, selected_fixedmods, selected_varmods, selected_enzyme, dbFile);
        // Create a progressbar, a TextArea and a dialog to house the area and bar.
        final JDialog progressDialog = new JDialog(parentFrame, "OMSSA progress monitor...", true);
        progressDialog.setLayout(new BorderLayout());
        final JProgressBar progress = new JProgressBar();
        JButton cancel = new JButton("Cancel");
        progress.setStringPainted(true);
        progress.setString("");
        progress.setMaximumSize(new Dimension(progress.getMaximumSize().width, progress.getPreferredSize().height));
        JPanel progPanel = new JPanel();
        progPanel.add(progress);
        progPanel.add(Box.createVerticalStrut(10));
        progPanel.add(Box.createHorizontalStrut(10));
        progPanel.add(cancel);
        progressDialog.add(progPanel);
        progress.setIndeterminate(true);
        progress.setString("OMSSA progressbar monitor...");
        final OmssaWorker ow = new OmssaWorker(needFormatDB, fpb, opb, progress, progressDialog);
        cancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                ow.cancelRun();
            }
        });
        ow.start();
        progressDialog.pack();
        progressDialog.setLocationRelativeTo(this);
        progressDialog.setVisible(true);
    }

    private void omssaRunCompleted(JDialog aDialog) {
        aDialog.setVisible(false);
        aDialog.dispose();
        JOptionPane.showMessageDialog(this, "OMSSA search done!", "Search complete!", JOptionPane.INFORMATION_MESSAGE);
    }

    private void omssaRunCancelled(JDialog aDialog) {
        aDialog.setVisible(false);
        aDialog.dispose();
        JOptionPane.showMessageDialog(this, new String[]{"OMSSA search cancelled!"}, "Search cancelled", JOptionPane.WARNING_MESSAGE);
    }


    /**
     * This method is called whenever the 'exit' button is pressed.
     */
    private void exitPressed() {
        int result = JOptionPane.showConfirmDialog(this, "Are you sure you want to quit?", "Confirm exit", JOptionPane.YES_NO_OPTION);
        if(result == JOptionPane.YES_OPTION) {
            close(0);
        }
    }

    /**
     * This method should be called whenever 'browse' has been pressed for the
     * OMSSA location. It auto-validates the OMSSA location.
     *
     * @param   aExitOnCancel boolean to indicate whether the software should exit
     *                        if the user presses 'cancel' (i.e.: does not provide
     *                        a correct OMSSA location).
     */
    private void browseOMSSALocationPressed(boolean aExitOnCancel) {
        // First check whether a file has already been selected.
        // If so, start from that file's parent.
        File startLocation = null;
        if(txtOMSSA_location != null && txtOMSSA_location.getText()!= null && !txtOMSSA_location.getText().trim().equals("")) {
            File temp = new File(txtOMSSA_location.getText());
            if(temp.exists() && temp.isDirectory()) {
                startLocation = temp;
            }
        }
        boolean lbContinue = true;
        while(lbContinue) {
            JFileChooser fc = new JFileChooser(startLocation);
            fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            int result = fc.showOpenDialog(OMSSAGUI.this);
            if (result == JFileChooser.APPROVE_OPTION) {
                File file = fc.getSelectedFile();
                if(validateOMSSALocation(file)) {
                    txtOMSSA_location.setText(file.getAbsolutePath());
                    lbContinue = false;
                    newOMSSALocationSpecified(file);
                } else {
                    JOptionPane.showMessageDialog(this, new String[]{"Incorrect OMSSA home folder specified.", "Please try again, or press cancel to exit."}, "Incorrect OMSSA folder specified!", JOptionPane.WARNING_MESSAGE);
                }
                startLocation = file;
            } else if(result == JFileChooser.CANCEL_OPTION) {
                if(aExitOnCancel) {
                    close(0);
                } else {
                    break;
                }
            }
        }
    }

    /**
     * This method terminates the program.
     *
     * @param aStatus   int with the completion status. Following C-convention,
     *                  '0' means: all OK, anything higher means trouble.
     */
    private void close(int aStatus) {
        if(iGUICreated && txtOMSSA_location.getText() != null) {
            String temp = txtOMSSA_location.getText().trim();
            if(!temp.equals("")) {
                saveOMSSALocationFile();
            }
        }
        this.setVisible(false);
        parentFrame.setVisible(false);
        parentFrame.dispose();
        System.out.println("\n\nThank you for using OMSSA GUI!\n\n");
        System.exit(aStatus);
    }

    /**
     * This method loads the necessary parameters for populating
     * (part of) the GUI from a properties file.
     *
     * @param aFile File with the relevant properties file.
     * @return Properties with the loaded properties.
     */
    private Properties loadProperties(File aFile) {
        Properties screenProps = new Properties();
        try {
            FileInputStream fis = new FileInputStream(aFile);
            if(fis != null) {
                screenProps.load(fis);
                fis.close();
            } else {
                throw new IllegalArgumentException("Could not read the file you specified ('" + aFile.getAbsolutePath() + "')!");
            }
        } catch(IOException ioe) {
            JOptionPane.showMessageDialog(this, new String[] {"Unable to read file: " + aFile.getName(), ioe.getMessage()}, "Unable to read file!", JOptionPane.WARNING_MESSAGE);
        }
        return screenProps;
    }

    /**
     * This method takes the specified properties instance and reads the values
     * for (some of) the GUI components from it. Keys are defined as constants on this
     * class.
     *
     * @param aProps    Properties with the values for the GUI.
     */
    private void setScreenProps(Properties aProps) {
        String temp = aProps.getProperty(DATABASE_FILE);
        if(temp == null) {
            temp = "";
        }
        txtDatabaseFile.setText(temp.trim());

        // First fixed modifications.
        temp = aProps.getProperty(FIXED_MODIFICATIONS);
        // Clear the current selection.
        lstFixedMods.clearSelection();
        if(temp != null && !temp.trim().equals("")) {
            ArrayList tempMods = parseModificationLine(temp);
            int[] indices = new int[tempMods.size()];
            int counter = 0;
            for (Iterator lIterator = tempMods.iterator(); lIterator.hasNext();) {
                OMSSA_GUI_Modification mod = (OMSSA_GUI_Modification)lIterator.next();
                indices[counter] = mod.getNumber();
                counter++;
            }
            lstFixedMods.setSelectedIndices(indices);
        }

        temp = aProps.getProperty(VARIABLE_MODIFICATIONS);
        // Clear the current selection.
        lstVarMods.clearSelection();
        if(temp != null && !temp.trim().equals("")) {
            ArrayList tempMods = parseModificationLine(temp);
            int[] indices = new int[tempMods.size()];
            int counter = 0;
            for (Iterator lIterator = tempMods.iterator(); lIterator.hasNext();) {
                OMSSA_GUI_Modification mod = (OMSSA_GUI_Modification)lIterator.next();
                indices[counter] = mod.getNumber();
                counter++;
            }
            lstVarMods.setSelectedIndices(indices);
        }

        // Now the variable modifications.

        temp = aProps.getProperty(ENZYME);
        if (temp != null && !temp.equals("")){
            cmbEnzyme.setSelectedItem(temp.trim());
        }
        temp = aProps.getProperty(MISSED_CLEAVAGES);
        if(temp == null) {
            temp = "";
        }
        txtMissedCleavages.setText(temp.trim());
        temp = aProps.getProperty(PRECURSOR_MASS_TOLERANCE);
        if(temp == null) {
            temp = "";
        }
        txtPrecursorTolerance.setText(temp.trim());
        temp = aProps.getProperty(FRAGMENT_MASS_TOLERANCE);
        if(temp == null) {
            temp = "";
        }
        txtFragmentTolerance.setText(temp.trim());
        temp = aProps.getProperty(EVALUE_CUTOFF);
        if(temp == null) {
            temp = "";
        }
        txtEValue.setText(temp.trim());
        temp = aProps.getProperty(MAXIMUM_HITLIST_LENGTH);
        if(temp == null) {
            temp = "";
        }
        txtHitlist.setText(temp.trim());
        temp = aProps.getProperty(PRECURSOR_CHARGE_LOWER_BOUND);
        if(temp == null) {
            temp = "";
        }
        txtChargeLowerBound.setText(temp.trim());
        temp = aProps.getProperty(PRECURSOR_CHARGE_UPPER_BOUND);
        if(temp == null) {
            temp = "";
        }
        txtChargeUpperBound.setText(temp.trim());
        temp = aProps.getProperty(PRECURSOR_CHARGE_TO_CONSIDER_MULTIPLY_CHARGED_FRAGMENTS);
        if(temp == null) {
            temp = "";
        }
        txtPrecChargeForFragmentCharge.setText(temp.trim());
    }

    /**
     * This method saves the OMSSA location file in the user's home folder.
     */
    private void saveOMSSALocationFile() {
        String homeFolder = System.getProperty("user.home");
        File folder = new File(homeFolder);
        if(!folder.exists()) {
            JOptionPane.showMessageDialog(this, new String[]{"Unable to find user home folder: '" + homeFolder + "'!", "Could not save OMSSA location."}, "Could not find user home folder!", JOptionPane.WARNING_MESSAGE);
        } else {
            File output = new File(folder, OMSSA_LOCATION_FILE);
            try {
                BufferedWriter bw = new BufferedWriter(new FileWriter(output));
                bw.write(txtOMSSA_location.getText().trim());
                bw.flush();
                bw.close();
            } catch(IOException ioe) {
                JOptionPane.showMessageDialog(this, new String[]{"Unable to write file: '" + ioe.getMessage() + "'!", "Could not save OMSSA location."}, "Could not write OMSSA location file!", JOptionPane.WARNING_MESSAGE);
            }
        }
    }

    /**
     * This method loads the OMSSA location file from the user's home folder.
     * <b>Note</b> that if the file is not found, or it is empty, the returned
     * String will be 'null'!
     *
     * @return  String  with the OMSSA location, as specified in the file, or 'null'
     *                  if the file could not be found, or is empty.
     */
    private String loadOMSSALocationFile() {
        String result = null;

        String homeFolder = System.getProperty("user.home");
        File folder = new File(homeFolder);
        if(folder.exists()) {
            File input = new File(folder, OMSSA_LOCATION_FILE);
            try {
                BufferedReader br = new BufferedReader(new FileReader(input));
                String line = null;
                while((line = br.readLine()) != null) {
                    // Skip empty lines and comment ('#') lines.
                    line = line.trim();
                    if(line.equals("") || line.startsWith("#")) {
                        continue;
                    } else {
                        result = line;
                    }
                }
                br.close();
            } catch(IOException ioe) {

            }
        }
        return result;
    }

    /**
     * This method saves the screen properties to the specified
     * properties file.
     *
     * @param   aOutputFile File to output to.
     */
    private void saveScreenProps(File aOutputFile) {
        StringBuffer output = new StringBuffer();
        String host = " @ ";
        try {
            host += InetAddress.getLocalHost().getHostName();
        } catch(UnknownHostException uhe) {
            // Disregard. It's not so bad if we can not report this.
        }
        // Write the file header.
        output.append("# ------------------------------------------------------------------" +
                    "\n# OMSSA GUI settings file" +
                    "\n#" +
                    "\n# Originally saved by: " + System.getProperty("user.name") + host +
                    "\n#                  on: " + sdf.format(new Date()) +
                    "\n#                  as: " + aOutputFile.getName() +
                    "\n# ------------------------------------------------------------------\n");
        // Write the values.
        output.append(DATABASE_FILE + "=" + txtDatabaseFile.getText().trim().replace('\\', '/') + "\n");
        output.append(ENZYME + "=" + cmbEnzyme.getSelectedItem().toString().trim() + "\n");
        // Fixed modifications first.
        output.append(FIXED_MODIFICATIONS + "=");
        Object[] selection = lstFixedMods.getSelectedValues();
        for (int i = 0; i < selection.length; i++) {
            OMSSA_GUI_Modification mod = (OMSSA_GUI_Modification)selection[i];
            if(i > 0) {
                output.append(MODIFICATION_SEPARATOR);
            }
            output.append(mod.getNumber() + ". " + mod.getName());
        }
        output.append("\n");
        // Variable modifications next.
        output.append(VARIABLE_MODIFICATIONS + "=");
        selection = lstVarMods.getSelectedValues();
        for (int i = 0; i < selection.length; i++) {
            OMSSA_GUI_Modification mod = (OMSSA_GUI_Modification)selection[i];
            if(i > 0) {
                output.append(MODIFICATION_SEPARATOR);
            }
            output.append(mod.getNumber() + ". " + mod.getName());
        }
        output.append("\n");
        output.append(MISSED_CLEAVAGES + "=" + txtMissedCleavages.getText().trim() + "\n");
        output.append(PRECURSOR_MASS_TOLERANCE + "=" + txtPrecursorTolerance.getText().trim() + "\n");
        output.append(FRAGMENT_MASS_TOLERANCE + "=" + txtFragmentTolerance.getText().trim() + "\n");
        output.append(EVALUE_CUTOFF + "=" + txtEValue.getText().trim() + "\n");
        output.append(MAXIMUM_HITLIST_LENGTH + "=" + txtHitlist.getText().trim() + "\n");
        output.append(PRECURSOR_CHARGE_LOWER_BOUND + "=" + txtChargeLowerBound.getText().trim() + "\n");
        output.append(PRECURSOR_CHARGE_UPPER_BOUND + "=" + txtChargeUpperBound.getText().trim() + "\n");
        output.append(PRECURSOR_CHARGE_TO_CONSIDER_MULTIPLY_CHARGED_FRAGMENTS + "=" + txtPrecChargeForFragmentCharge.getText().trim() + "\n");

        BufferedWriter bw = null;
        try{
            bw = new BufferedWriter(new FileWriter(aOutputFile));
            bw.write(output.toString());
            bw.flush();
            JOptionPane.showMessageDialog(this, "Written settings to file '" + aOutputFile.getAbsolutePath() + "'.", "Settings saved.", JOptionPane.INFORMATION_MESSAGE);
        } catch(IOException ioe) {
            JOptionPane.showMessageDialog(this, new String[]{"Error writing settings to file:", ioe.getMessage()}, "Save failed.", JOptionPane.ERROR_MESSAGE);
        } finally {
            if(bw != null) {
                try {
                    bw.close();
                } catch(IOException ioe) {
                    // Nothing to be done.
                }
            }
        }
    }

    /**
     * This method parses a modification line from a properties file.
     *
     * @param aLine String with the modification line from the properties file.
     * @return  ArrayList with the parsed OMSSA_GUI_Modification instances.
     */
    private ArrayList parseModificationLine(String aLine) {
        ArrayList result = new ArrayList();
        // Split the different modifications.
        int start = -1;
        while((start = aLine.indexOf(MODIFICATION_SEPARATOR)) >= 0) {
            String numberAndname = aLine.substring(0, start);
            aLine = aLine.substring(start+2);
            if(!numberAndname.trim().equals("")) {
                OMSSA_GUI_Modification mod = getModification(numberAndname);
                if(mod != null) {
                    result.add(mod);
                } else {
                    JOptionPane.showMessageDialog(this, "Unable to find modification '" + numberAndname + "'!", "Modification not found!", JOptionPane.WARNING_MESSAGE);
                }
            }
        }
        // Fence post.
        if(!aLine.trim().equals("")) {
            OMSSA_GUI_Modification mod = getModification(aLine);
            if(mod != null) {
                result.add(mod);
            } else {
                JOptionPane.showMessageDialog(this, "Unable to find modification '" + aLine + "'!", "Modification not found!", JOptionPane.WARNING_MESSAGE);
            }
        }
        return result;
    }

    /**
     * This methdo parses a modification from the specified number and name String.
     *
     * @param aNumberAndName    String with the modification (style: number + ". " + name)
     * @return OMSSA_GUI_Modification instance with the modification,
     *                                or 'null' if it could not be found.
     */
    private OMSSA_GUI_Modification getModification(String aNumberAndName) {
        // Parse number and name.
        int start = aNumberAndName.indexOf(".");
        String numberString = aNumberAndName.substring(0, start).trim();
        int number = Integer.parseInt(numberString);
        String name = aNumberAndName.substring(start+1).trim();
        OMSSA_GUI_Modification mod = modifications.getModificationByName(name, number);
        return mod;
    }

    /**
     * This method should be called whenever a new OMSSA location has been specified.
     *
     * @param aFile    File with the new (and validated!) OMSSA location.
     */
    private void newOMSSALocationSpecified(File aFile) {
        try {
            modifications = new OMSSAModificationReader(aFile);
        } catch(IOException ioe) {
            JOptionPane.showMessageDialog(this, new String[]{"Unable to read modification files", ioe.getMessage()}, "Failed to read modifications", JOptionPane.ERROR_MESSAGE);
            ioe.printStackTrace();
        } catch(XmlPullParserException xppe) {
            JOptionPane.showMessageDialog(this, new String[]{"Unable to parse modification files", xppe.getMessage()}, "Failed to parse modifications", JOptionPane.ERROR_MESSAGE);
            xppe.printStackTrace();
        }
        refreshModLists();
        txtOMSSA_location.setText(aFile.getAbsolutePath());
        enableGUI(true);
    }

    /**
     * This method will refresh the modification lists.
     */
    private void refreshModLists() {
        if(iGUICreated) {
            DefaultListModel dlm = (DefaultListModel)lstFixedMods.getModel();
            dlm.clear();
            populateListModel(dlm, modifications.getModifications());
            dlm = (DefaultListModel)lstVarMods.getModel();
            dlm.clear();
            populateListModel(dlm, modifications.getModifications());
        }
    }


    /**
     * Convenience method to populate a DefaultListModel with data from an Object[].
     *
     * @param aModel DefaultListModel to populate
     * @param aData Object[] with the source data.
     */
    private void populateListModel(DefaultListModel aModel, Object[] aData) {
        for (int i = 0; i < aData.length; i++) {
            aModel.addElement(aData[i]);
        }
    }

    /**
     * This method should be called whenever the OMSSA location currently specified is
     * invalid ('null', empty, or the wrong folder).
     */
    private void invalidOMSSALocationSpecified() {
        enableGUI(false);
        JOptionPane.showMessageDialog(this, new String[] {"Your current OMSSA home folder is incorrect.", "Please specify a correct folder to start the program"}, "Incorrect OMSSA folder specified!", JOptionPane.WARNING_MESSAGE);
        browseOMSSALocationPressed(true);
    }

    /**
     * This method can be called to enable or disable all the GUI components on the display.
     *
     * @param aEnable boolean to indicate whether all components should be enabled ('true'),
     *                 or disabled ('false').
     */
    private void enableGUI(boolean aEnable) {
        if(iGUICreated) {
            btnBrowseDatabase.setEnabled(aEnable);
            btnBrowseMGF.setEnabled(aEnable);
            btnBrowseOMSSA_location.setEnabled(aEnable);
            btnBrowseOutput.setEnabled(aEnable);

            txtChargeLowerBound.setEnabled(aEnable);
            txtChargeUpperBound.setEnabled(aEnable);
            txtDatabaseFile.setEnabled(aEnable);
            txtEValue.setEnabled(aEnable);
            txtFragmentTolerance.setEnabled(aEnable);
            txtHitlist.setEnabled(aEnable);
            txtMGFFile.setEnabled(aEnable);
            txtMissedCleavages.setEnabled(aEnable);
            txtOMSSA_location.setEnabled(aEnable);
            txtOutputFile.setEnabled(aEnable);
            txtPrecChargeForFragmentCharge.setEnabled(aEnable);
            txtPrecursorTolerance.setEnabled(aEnable);

            lstFixedMods.setEnabled(aEnable);
            lstVarMods.setEnabled(aEnable);
        }
    }

    /**
     * This method validates whether the specified folder is an OMSSA root folder.
     *
     * @param aFile File with the OMSSA root folder
     * @return  boolean to indicate whether the OMSSA root folder has been selected.
     */
    private boolean validateOMSSALocation(File aFile) {
        boolean result = false;
            if(aFile.exists() && aFile.isDirectory()) {
                String[] fileNames = aFile.list();
            int count = 0;
            for (int i = 0; i < fileNames.length; i++) {
                String lFileName = fileNames[i];
                if(lFileName.equalsIgnoreCase("mods.xml")) {
                    count++;
                } else if(lFileName.equalsIgnoreCase("omssa.xsd")) {
                    count++;
                }
            }

            if(count > 1) {
                result = true;
            }
        }

        return result;
    }

    private class OmssaWorker extends SwingWorker {

        private boolean needFormatDB = false;
        private JProgressBar progress = null;
        private FormatdbProcessBuilder fpb = null;
        private OmssaguiProcessBuilder opb = null;
        private OmssaguiProcessBuilder currentProcess = null;
        private JDialog dialog = null;

        private boolean isCancelled = false;

        public OmssaWorker(boolean aNeedFormatDB, FormatdbProcessBuilder aFpb, OmssaclProcessBuilder aOpb, JProgressBar aProgress, JDialog aDialog) {
            needFormatDB = aNeedFormatDB;
            fpb = aFpb;
            opb = aOpb;
            progress = aProgress;
            dialog = aDialog;
        }

        public Object construct() {
            if(needFormatDB && !isCancelled) {
                progress.setString("Formatting database...");
                currentProcess = fpb;
                fpb.startProcess();
            }
            if(!isCancelled) {
                progress.setString("Running OMSSA...");
                currentProcess = opb;
                opb.startProcess();
            }
            currentProcess = null;
            return "";
        }

        public void finished() {
            // We're done.
            if(!isCancelled) {
                OMSSAGUI.this.omssaRunCompleted(dialog);
            } else {
                OMSSAGUI.this.omssaRunCancelled(dialog);
            }
        }

        public void cancelRun() {
            // Start cancelling of the process.
            isCancelled = true;
            progress.setString("Cancelling...");
            if(currentProcess != null) {
                currentProcess.endProcess();
            }
        }
    }
}
